#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define TAMANIO_BUFFER 5

void saveErrors(char* message, char* ref)
{
     FILE* fdErr=fopen("./errores.txt","w");
     fprintf(fdErr,message,ref);
     fclose(fdErr);
     exit(0);
}     

void printValues(char modo, int stdOutput, char* output, int value1, int value2, int endLine, char letra)
{
     FILE *fdOut;
     
     //si es salida estandar guardamos caracteres en el modo que corresponda
     if(stdOutput==1){
       if(modo=='x'){
         if(endLine==1) printf("%x%x\n",value2,value1);
         else           printf("%x%x",value2,value1);
       }
       else{
         if(endLine==1) printf("%c\n",letra);
         else           printf("%c",letra);
       }          
     }
     else{
       fdOut=fopen(output,"a");
       if(modo=='x'){
	       if(endLine==1) fprintf(fdOut,"%x%x\n",value2,value1);
	       else	          fprintf(fdOut,"%x%x",value2,value1);
       }
       else{
           if(endLine==1) fprintf(fdOut,"%c\n",letra);
           else           fprintf(fdOut,"%c",letra);
       }
       fclose(fdOut);
     }
}

int getIntegerValue(char letra)
{
    int i;
    switch(letra)
    {
       case 'a': i=10;break;
       case 'b': i=11;break;
       case 'c': i=12;break;
       case 'd': i=13;break;
       case 'e': i=14;break;
       case 'f': i=15;break;
       default: break;
    }
    return i;
}

void inverseProcess (unsigned char letra1, unsigned char letra2, int stdOutput, char* output)
{
   unsigned char letraFinal;
   int letraInt,letraFinalInt,scale,i=0,value1=0,value2=0;
   
   //procesamos la primer letra hexadecimal
   if((letra1=='a')||(letra1=='b')||(letra1=='c')||(letra1=='d')||(letra1=='e')||(letra1=='f')){
      letraInt=getIntegerValue(letra1);
      letraInt<<=4;
   }
   else{
      letra1<<=4;
      letraInt=(int)letra1;
   }
   
   //procesamos la segunda letra hexadecimal
   if((letra2=='a')||(letra2=='b')||(letra2=='c')||(letra2=='d')||(letra2=='e')||(letra2=='f'))
      value1=getIntegerValue(letra2);
   else{
      for (scale=1; letra2!=0; letra2>>=1){
         if ( letra2 & 1){
            if(i<4) value1+=scale;
            else    value2+=scale;
         }
         i++;
         if(i==4) scale=1;
         else     scale*=2;
      }
   }
   
   //hacemos un OR bit a bit entre las dos partes para formar el caracter 
   //(casteado en Integer) buscado y lo convertimos a char
   letraFinalInt= letraInt | value1;
   letraFinal=(char)letraFinalInt;
   
   //guardamos el char con value1=0, value2=0 y endLine=0 (todos los carateres de corrido)
   printValues('b',stdOutput,output,0,0,0,letraFinal);
}

void process (unsigned char letra, int stdOutput, char* output, int endLine)
{
      int i=0,scale,value1=0,value2=0;
      for (scale=1; letra!=0; letra>>=1){
          if ( letra & 1){
               if(i<4) value1+=scale;
               else    value2+=scale;
          }
          i++;
          if(i==4) scale=1;
          else     scale*=2;
      }
      
      //guardamos los valores segun los valores new line y stdOut
      //letra=0 ya que no estamos en modo -b
      printValues('x',stdOutput,output,value1,value2,endLine,0);
      
}

void binToHex(int stdInput, char* input, int stdOutput, char* output, int lenght, char* frase)
{
   unsigned char letra;
   int i,count=0,endLine=0;
   FILE *fdOut,*fdIn;
   
   //empezamos a procesar los datos de entrada
   if(stdInput==1)
   { 
       //por cada letra leida del teclado llamamos a process, seteando 
       //el parametro "endLine" segn corresponda
       i=0;
       letra=frase[i];
       while(letra!='\0'){
          count++;
          if(count==lenght){endLine=1; count=0;}
          process(letra,stdOutput,output,endLine);
          i++;
          letra=frase[i];
          if(endLine==1) endLine=0;
       }
   }
   else
   {      
       //leemos desde archivo
       fdIn=fopen(input,"r");
       if (fdIn==NULL)
       	 //indicamos inexistencia del archivo
   	 saveErrors("No se encuentra el archivo de entrada: %s",input);
       
       //en caso de existir el archivo de entrada, creamos y, en caso de que 
       //ya exista, borramos el contenido del archivo salida en caso de 
       //contener caracteres almacenados anteriormente
       fdOut= fopen(output,"w");
       fclose(fdOut);
       
       //empezamos a procesar el archivo
       letra=fgetc(fdIn);
       while(feof(fdIn)==0){
          count++;
          if(count==lenght){endLine=1; count=0;}
          process(letra,stdOutput,output,endLine);
          letra=fgetc(fdIn);
          if(endLine==1) endLine=0;
       }
       fclose(fdIn);
   }
}

void hexToBin(int stdInput, char* input, int stdOutput, char* output, char* frase)
{
   int j=0;
   unsigned char letra1,letra2;
   FILE *fdIn,*fdOut;
   
   if(stdInput==1){
       while(frase[j]!='\0'){
          letra1=frase[j];     j++;
          letra2=frase[j];     j++;
          
          //procesamos ambos caracteres hexadecimales
          inverseProcess(letra1,letra2,stdOutput,output);
      }
   }
   else{
       //leemos desde archivo
       fdIn = fopen(input,"r");
       if (fdIn==NULL)
       	 //indicamos inexistencia del archivo de entrada
   	 saveErrors("No se encuentra el archivo de entrada: %s",input);
   
       //en caso de existir el archivo de entrada, creamos y, en caso de que 
       //ya exista, borramos el contenido del archivo salida en caso de 
       //contener caracteres almacenados anteriormente
       fdOut= fopen(output,"w");
       fclose(fdOut);
       
       //empezamos a procesar el archivo
       letra1=fgetc(fdIn);
       while(feof(fdIn)==0){
          letra2=fgetc(fdIn);
          
          //procesamos ambos caracteres hexadecimales
          inverseProcess(letra1,letra2,stdOutput,output);
          
          //leemos los siguientes valores hexadecimales
          letra1=fgetc(fdIn);
          if(letra1=='\n') letra1=fgetc(fdIn);
       }
       fclose(fdIn);
   }
}

char* leerLinea()
{
   char* acumulador = malloc(TAMANIO_BUFFER);
   char* end;
   char buf[TAMANIO_BUFFER+1];
   int acumuladorSize = TAMANIO_BUFFER+1;
   *acumulador = '\0';

   while ( !feof(stdin) )
   {
     fgets(buf, TAMANIO_BUFFER+1, stdin);
     /* fgets agrega un caracter al final de la lectura así que leo TAMANIO_BUFFER+1
      * ver: http://en.wikipedia.org/wiki/Fgets */
     strcat(acumulador, buf);
     end = strchr(acumulador, '\n');
     
     if (end == NULL)
     {
     	acumuladorSize += TAMANIO_BUFFER+1;
     	acumulador = realloc(acumulador, acumuladorSize);
     }
     else
     {
     	// fin de linea
     	*end = '\0';
     	break;
     }
   }
   
   return acumulador;
}

int mostrarAyuda()
{
	printf("\
Usage:\n\
  tp0 -h\n\
  tp0 -V\n\
  tp0 [options]\n\
Options:\n\
  -h, --help         Print this information.\n\
  -V, --version      Print version and quit.\n\
  -x, --hexdump      Hexdump mode: encode the input (binary file) in hexadecimal\n\
                     format (default).\n\
  -b, --binary       Binary mode: decode the input (hexdump file) in binary\n\
                     format.\n\
  -i, --input        Path to input file (defaults to stdin).\n\
  -o, --output       Path to output file (defaults to stdout).\n\
  -l, --length       Number of hex bytes per line (line length) at the output in\n\
                     hexdump mode (0 = default = no limit).\n\
Examples:\n\
  tp0\n\
  tp0 -i -\n\
  tp0 -i - -o - -x\n\
  tp0 -b\n\
  tp0 -i /dev/random -o /tmp/random-hexdump -l 16\n\
	\n");
	
	return 0;
}

int mostrarVersion()
{
	printf("tp0 V1.0 by Marcelo Suvia, Emiliano Ritiro, Diego Sisto\n");
	
	return 0;
}

int main(int argc, char *argv[])
{
  //opciones por default
  int lenght=0;
  char modo='x';
  int stdInput=1,stdOutput=1;
    
  //seteo de variables de entrada
  char letra;
  char *input,*output,*frase;
  int i;
  
  for(i=1;i<argc;i++)
  {
     if (argv[i][0]=='-' && argv[i][1]!='-')
     { // argumentos con -<letra>, con <letra> distinto de -
         letra=argv[i][1];
         switch(letra)
         {
            case 'i': if((i+1<argc)&&(argv[i+1][0]!= '-')){ input=argv[i+1]; stdInput=0;}
                      break;
            case 'o': if((i+1<argc)&&(argv[i+1][0]!= '-')){ output=argv[i+1]; stdOutput=0;}
                      break;
            case 'l': if((i+1<argc)&&(argv[i+1][0]!= '-')) lenght=atoi(argv[i+1]);
                      break;
            case 'b': modo='b';
                      break;
            case 'h': modo='h';
            		  break;
            case 'V': modo='v';
            		  break;
            default : saveErrors("Parametro invalido: %s\n",argv[i]);
         }
         //si es una parametro del tipo -<letra> verificamos que no contenga mas nada
         if(strlen(argv[i])> 2)
            saveErrors("Parametro invalido: %s\n",argv[i]);
     }
     
     if (strcmp(argv[i], "--help")==0)
     {
     	modo='h';
     }
     
     if (strcmp(argv[i], "--version")==0)
     {
     	modo='v';
     }
     
     if (strcmp(argv[i], "--binary")==0)
     {
     	modo='b';
     }
     
     if (strcmp(argv[i], "--input")==0)
     {
     	if((i+1<argc)&&(argv[i+1][0]!= '-')){ input=argv[i+1]; stdInput=0;}
     }
     
     if (strcmp(argv[i], "--output")==0)
     {
     	if((i+1<argc)&&(argv[i+1][0]!= '-')){ output=argv[i+1]; stdOutput=0;}
     }
     
     if (strcmp(argv[i], "--length")==0)
     {
     	if((i+1<argc)&&(argv[i+1][0]!= '-')) lenght=atoi(argv[i+1]);
     }
  }
  
  // veo si es el modo de ayuda
  if (modo == 'h')
  {
  	 mostrarAyuda();
  	 return 0;
  }
  
  // veo si es el modo de version
  if (modo == 'v')
  {
  	 mostrarVersion();
  	 return 0;
  }
  
  //obtenemos la frase a convertir en caso de que sea entrada por teclado
  if(stdInput==1)
     frase = leerLinea();
    
  //generamos el archivo segun los requerimientos
  if(modo=='x') binToHex(stdInput,input,stdOutput,output,lenght,frase);
  else          hexToBin(stdInput,input,stdOutput,output,frase);
  
  return 0;
}
